package com.dong.auth.security;

import com.dong.auth.filter.LoginFilter;
import com.dong.auth.security.authentication.Authentication;
import com.dong.auth.security.authentication.holder.DongAuthenticationHolder;
import com.dong.auth.security.exception.AuthenticationException;
import com.dong.auth.security.exception.AccessDeniedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 *
 * 自定义安全过滤器链执行代理，
 *
 * 安全过滤器的执行逻辑都定义在这里
 *
 * @author zhaodongchao
 * @date 2021/10/12 20:59
 * @version V1.0
 */
public class DongSecurityFilterChainProxy extends GenericFilterBean {
    private static final Logger logger = LoggerFactory.getLogger(DongSecurityFilterChainProxy.class);

    private static final String FILTER_PROCESSED = DongSecurityFilterChainProxy.class.getName().concat(".PROCESSED");

    private final List<DongSecurityFilterChain> filterChains ;

    public DongSecurityFilterChainProxy(List<DongSecurityFilterChain> filterChains) {
        this.filterChains = filterChains;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //FILTER_PROCESSED放入请求中，防止过滤器链在一次请求中被重复执行
        boolean clearContext = request.getAttribute(FILTER_PROCESSED) == null;
        if (!clearContext) {
            doProcessFilters(request, response, chain);
            return;
        }

        try{
            request.setAttribute(FILTER_PROCESSED,Boolean.TRUE);
            doProcessFilters(request,response,chain);
        }catch (RuntimeException e){
            logger.error(e.getMessage());
            //TODO 执行过滤器失败，跳转到错误页面
        }finally {
            //清除登录信息
            request.removeAttribute(FILTER_PROCESSED);
        }
    }

    private void doProcessFilters(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        //TODO 检查请求路径是否包含不合法的字符
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        logger.info("{}:自定义安全过滤链开始执行啦",httpServletRequest.getRequestURI());
        //通过请求来获取需要执行的过滤器
        List<Filter> filters = getFilters(httpServletRequest);
        if (null == filters || filters.isEmpty()){
            //如果没有找到合适的过滤器链，就默认往下执行
            chain.doFilter(request,response);
            return;
        }
        //如果有合适的过滤器链,构造一个过滤器执行器。并在这里处理安全认证和授权的异常
        try {
            VirtualFilterChain virtualFilterChain = new VirtualFilterChain(chain, filters);
            virtualFilterChain.doFilter(request,response);
        }catch (AccessDeniedException e) {
            e.printStackTrace();
            logger.error("鉴权失败：{}", e.getMessage());
            //开始处理权限异常
            handleAccessDeniedException(httpServletResponse);
        } catch (AuthenticationException e) {
            logger.error("鉴权失败:{}",e.getMessage());
            handleAuthenticationException(httpServletResponse);
        }

    }
    private void handleAuthenticationException(HttpServletResponse response) throws IOException {
        Authentication authentication = DongAuthenticationHolder.get();
        if (authentication == null || !authentication.isAuthenticated()){
            //此时没有登录，需要进行登录操作
            //TODO 缓存当前没有权限访问的请求，等登录成功之后继续访问当前请求
            String loginForm = response.encodeRedirectURL(LoginFilter.LOGIN_PROCESS_URL);
            response.sendRedirect(loginForm);
        }
    }

    private void handleAccessDeniedException(HttpServletResponse response) throws IOException {
        if (response.isCommitted()) {
            logger.trace("Did not write to response since already committed");
            return;
        }
        //返回 403 错误码
        logger.debug("Responding with 403 status code");
        response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
    }

    /**
     * 通过请求查找到第一个符合条件的DongSecurityFilterChain
     *
     * @param request http请求
     * @return 对于当前请求可执行的过滤器链，没有匹配到则返回null
     */
    private List<Filter> getFilters(HttpServletRequest request) {
        for (DongSecurityFilterChain filterChain : this.filterChains){
            if (filterChain.matches(request)){
                return filterChain.getFilters();
            }
        }
        return null ;
    }

    private static final class VirtualFilterChain implements FilterChain {
        /**
         * Servlet 服务器的过滤器FilterChain
         */
        private final FilterChain originalChain;

        /**
         * 本次需要执行的Filter集合
         */
        private final List<Filter> additionalFilters;

        /**
         * 本次需要执行的Filter的个数
         */
        private final int size;

        /**
         * 当前执行的位置
         */
        private int currentPosition = 0;

        private VirtualFilterChain(FilterChain originalChain, List<Filter> additionalFilters) {
            this.originalChain = originalChain;
            this.additionalFilters = additionalFilters;
            this.size = additionalFilters.size();
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            if (size == currentPosition){
                originalChain.doFilter(request,response);
                return;
            }
            this.currentPosition ++ ;

            Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
            logger.info(String.format("请求:%s ,正在执行过滤器： %s (%d/%d)",((HttpServletRequest) request).getRequestURI(), nextFilter.getClass().getSimpleName(), this.currentPosition, this.size));

            nextFilter.doFilter(request,response,this);
        }
    }
}
